#!/usr/bin/env python
# -*- coding: utf-8 -*-
from StringIO import StringIO
import re
import pdb
import types
from core.ide.datamangager import DataMangager
from core.ide.toolsmanager import ToolsManager
from core.ui.centralpanel import CentralPanel
import os
import subprocess
import cPickle as pickle
import base64
import traceback
PROJECT_PATH=None

regPorperty=".([a-zA-Z0-9_]+)[ 	]*="

RULES={
    "class": re.compile("class[ 	]+([a-zA-Z0-9_]+)"),
    "def": re.compile("def[ 	]+([a-zA-Z0-9_]+)\(([^)]*)\)"),
    "property": re.compile("self"+regPorperty),
    "var": re.compile("(^[^\.]+)[ 	]*="),
    "from": re.compile("from[ \t]([^ ]+)[ \t]"+"import[ \t]([^ ]+)([ \t]as[ \t]([^ ]+))?"),
    "import": re.compile("import[ \t]([^ ]+)([ \t]as[ \t]([^ ]+))?"),
}
RULES_ORDER=["class","def","property","var","from","import"]

class CodeReader:
    def __init__(self):
        global PROJECT_PATH
        if ToolsManager().hasTool(ToolsManager().ID_PROJECT):
            project=ToolsManager().getToolById(ToolsManager().ID_PROJECT).getDefaultProject()
            PROJECT_PATH=os.path.abspath(project.getBaseDir())
        
        self.line=0
        self._error=[]
        self._line=[]
        self._noparent=False
        self._global = {}
    

    def getGlobal(self):
        return self._global

    def read(self,text,path=""):
        #try:
            self._read(text,path)
        #except Exception as e:
        #    print ">ERROR READING:"
        #    print "\t-line number:"+str(len(self._line)+1)
        #    tb = traceback.format_exc()
        #    print tb
        #    raise e
        
        
    def _read(self,text,path):
        self.root=ContextObject()
        self._prev=self.root

        self.root.name="main"
        self.root.level=-1
        self._text=text
        strIO=StringIO(text)
        strIO.seek(0)
        while 1:
            try:
                line= strIO.next()
                self.line+= 1
            except:
                break
            line=line.replace("\n","").replace("\r","")
            ctxt=self.readLine(line)
            if ctxt and type(ctxt)==list:
                for c in ctxt:
                    c.filePath=path
                    #c.build()
                    self._line.append(c)
            elif ctxt:
                ctxt.filePath=path
                #ctxt.build()
                self._line.append(ctxt) 
            else:
                self._line.append(line)
                
            
    def readLine(self,line,lineNumber=-1):
        if not len(line) or line[0] == "#" or line == "":
            return None
        if lineNumber >- 1:
            self._prev=self.getLastContextByLine(lineNumber)
            self._noparent=True
            
        if line.find("#") > -1:
            line=line[:line.find("#")]  
        ctxt=None
        for name in RULES_ORDER:
            exp=RULES[name].search(line)
            if exp is not None:
                ctxt = getattr(self, "read_" + name)(line,exp)
                if ctxt and type(ctxt) == list:
                    self._prev=ctxt[-1]
                elif ctxt.type != "property":
                    self._prev = ctxt
   
        return ctxt
    
    def getLastContextByLine(self,lineNumber):
        while not isinstance(self._line[lineNumber],ContextObject):
            lineNumber -= 1
            if lineNumber < 0:
                return None
        return self._line[lineNumber]
      
    
        
    def read_line(self,line):
        pass   
    
    
    def getError(self):
        return self._error
    
    def addError(self,line,message):
        self._error.append(ContextError(line,message))
                
    def addToParent(self,ctxt):
        if self._noparent:
            return
        prev = self._prev
        while ctxt.level <= prev.level:
            prev=prev.parent
        prev.addChild(ctxt)

    def read_var(self,line,exp):
        contextObj=ContextObject()
        contextObj.name=exp.group(1)
        while contextObj.name[0] == " " or contextObj.name[0] == "\t":
            contextObj.name=contextObj.name[1:]
        contextObj.type="var"
        contextObj.level=self.getLevel(line)
        self.addToParent(contextObj)
        if line.find("=") >- 1:
            s=line.split("=")
            self.findTypeValue(contextObj,s[1])
        return contextObj

    def read_import(self,line,exp):
        exp2=RULES["from"].search(line)
        if exp2 is not None:
            return self.read_from(line, exp2)
        n=exp.group(1)
        ns=exp.group(1).split(",")
        
        if len(ns):
            _ctxs=[]
            for n in ns:
                ctx=self.createModule(n,n,self.getLevel(line))
                _ctxs.append(ctx)
            if exp.group(3):
                ctx.name=exp.group(3)

            self._global[ctx.name]=ctx
            return _ctxs
        else:
            if exp.group(3):
                return self.createModule(n,exp.group(3),self.getLevel(line))
            else:
                return self.createModule(n,n,self.getLevel(line))
        return None
    
   
    
    def read_from(self,line,exp):
        n=exp.group(2)
        ns=exp.group(2).split(",")
        
        if len(ns):
            _ctxs=[]
            for n in ns:
                ctx=self.createModule(exp.group(1),n,self.getLevel(line))
                _ctxs.append(ctx)
            if exp.group(4):
                ctx.name = exp.group(4)

            self._global[ctx.name]=ctx
            return _ctxs
        else:
            if exp.group(4):
                return self.createModule(exp.group(1), exp.group(4),self.getLevel(line))
            else:
                return self.createModule(exp.group(1), n,self.getLevel(line))
        return None


    def createModule(self,path,name,level=0):
        contextObj=ContextObject()
        contextObj.type="module"
        contextObj.name=name
        contextObj.path=path
        contextObj.level=level
        self.addToParent(contextObj)
        
        self._global[name]=contextObj
        return contextObj


    def read_class(self,line,exp):
        contextObj=ContextObject()
        contextObj.name=exp.group(1)
        
        contextObj.type="class"
        contextObj.level=self.getLevel(line)
        self.addToParent(contextObj)
        return contextObj
    
    def read_def(self,line,exp):
        contextObj=ContextObject()
        contextObj.name=exp.group(1)
        contextObj.level=self.getLevel(line)
        self.addToParent(contextObj)
        
        if contextObj.parent.type=="class":
            contextObj.type="method"
        else:
            contextObj.type="function"
        self.splitArg(exp.group(2),contextObj)
        return contextObj

    def read_property(self,line,exp):
        contextObj=ContextObject()
        contextObj.name=exp.group(1)
        
        contextObj.type="property"
        contextObj.level=self.getLevel(line)
        #self.addToParent(contextObj)
        
        c=self._prev.findParentByType("class")
        if not c:
            return contextObj
        c.addChild(contextObj)
        if line.find("=")>-1:
            s=line.split("=")
            self.value=s[1]
            self.findTypeValue(contextObj,s[1])
        #contextObj.parent=c
        return contextObj

    def splitArg(self,arg,ctxt):
        global regPorperty
        args=arg.split(",")
        isSelf=False
        if ctxt.parent.type=="class":
            isSelf=True
        for arg in args:
            c=ContextObject()
            c.name=arg
            c.type="argument"
            if arg.find("=")>-1:
                s=arg.split("=")
                self.findTypeValue(c,s[1])
                c.name=s[0]
            if isSelf:
                c.typeValue=ctxt.parent
                isSelf=False
                RULES["property"]=re.compile(c.name+regPorperty)
            ctxt.addChild(c)

    def findTypeValue(self,ctxt,value):
        if not value:
            return
        while value[0]==" " or value[0]=="\t":
            value=value[1:]
        if value=="None":
            return
        elif value[0]=='"' or value[0]=="'":
            ctxt.pyTypeValue=str
        elif value[0]=='[':
            ctxt.pyTypeValue=list
        elif value[0]=='{':
            ctxt.pyTypeValue=object
        else:
            if value.find("(")>0:
                value=value.split("(")[0]
            #values=value.split(".")
            if ctxt.parent:
                v=ctxt.parent.getChild(value)
                if not v:
                    v=self.root.getChild(value)
                
                if v:
                    ctxt.typeValue=v
            else:
                ctxt.typeValue = value
            #typeValue
    

    def getLevel(self,line):
        level=0
        while line[level]==" " or line[level]=="\t":
            level+=1
        return level


class ContextImport():
    def __init__(self):
        pass
    
class ContextError:
    def __init__(self,line,message):
        self.line=line
        self.message=message
        pass

class ContextObject(object):
    def __init__(self):
        self.level=0
        self.name=""
        self.ruleName=""
        self.type=None
        self.childs={}
        self.parent=None
        self.typeValue=None
        self.path=""
        self.filePath=""
        self.id=""
        self.pyTypeValue=None
        self.callReturn=None
        self.value=""
        self._ready=False
        self._update=False
        self.childBuilded=False
        self.isdBuilded=False
        self.genered=False
        self.doc=""
        #self.modifer
    
    def getDoc(self):
        return self.doc
    
    def canHasChild(self):
        return self.type=="class" or self.type=="method" or self.type=="function"
    
    def getStringTypeValue(self):
        if self.typeValue:
            return self.typeValue.name
        elif self.pyTypeValue:
            return str(self.pyTypeValue)
        else:
            return "None"
       
    def findParentByType(self,typeName):
        ctx=self
        while ctx.type!=typeName and ctx.parent:
            ctx=ctx.parent
        if(ctx.type==typeName):
            return ctx
        return None
    
    def read(self,tag,expResult):
        pass
    
    def addChild(self,child):
        
        self.childs[child.name]=child
        child.parent=self
    

    def getChilds(self):
        if not self.isdBuilded or self._update:
           self.build()
                
        elif self.pyTypeValue:
            self.getExternalInfos('childs -t "'+str(self.pyTypeValue)+'"')
        if isinstance(self.typeValue,ContextObject) and self.typeValue!=self:
            return self.typeValue.getChilds()
        #if not self.childBuilded:
        #    for ind in self.childs:
        #        if not self.childs[ind].isdBuilded:
        #            self.childs[ind].build()
    
        return self.childs.values()

    def _getFileContex(self,path,name):
        if not os.path.exists(path) and os.path.exists(path+".py"):
            path+=".py"
        elif not os.path.exists(path) and os.path.exists(path+".pyw"):
            path+=".pyw"
                
        if os.path.exists(path):
            dir_=os.path.isdir(path)
            if dir_ and os.path.exists(path+"/__init__.py"):
                path+="/__init__.py" 
            elif dir_ and os.path.exists(path+"/__init__.pyw"):
                path+="/__init__.pyw" 
            elif dir_:
                return False
            #self.filePath=path    
            cr=CodeReader()
            f=open(path,"r")
            cr.read(f.read(),path)
            f.close()
            ctx=cr.root.getChild(name)
            
            if ctx:
                #ctx.filePath=path
                #self.childs=ctx.childs
                self.typeValue=ctx
                self.type=ctx.type
            
            return True
            #TODO read File
        else:
            return False

    def getChild(self,name):
        if not self.isdBuilded:
            self.build()
        if self._update:
            self.getChilds()
        if self.childs.has_key(name):
            return self.childs[name]
        else:
            return None
        
    def build(self):
        self.isdBuilded=True
        if self.type=='module':
            self._update=True
            path="/".join(self.path.split("."))
            if not self._getFileContex(PROJECT_PATH+"/"+path,self.name):
                pathFile=os.path.dirname(self.filePath)
                if not self._getFileContex(pathFile+"/"+path,self.name):
                    self.getExternal()
        if self.genered:
            self.getExternal()
    
    def getExternal(self):
        m=self.name
        t=None
        if self.path and self.name!=self.path:
            m=self.path#+"."+self.name
            t=self.name
        
        obj=self
        while obj.parent and obj.parent.type=='module':
            obj=self.parent
            m=self.parent.name+"."+m
        cmd="childs -m "+m
        if t:
            cmd+=" -o "+t
        if self.getExternalInfos(cmd):
            self._update=False
    
    def removeChild(self,name):
        del self.childs[name]
    
    def getExternalInfos(self,cmd):
        global PROJECT_PATH
        pathPython=DataMangager().getValue("Python","Python Path")
        
        
            
        print "cmd context",cmd
        if pathPython!="":
            child = subprocess.Popen(pathPython+' "'+os.path.abspath(".")+'/extensions/langs/python/externalcontext.py" '+cmd, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
            out = child.stdout.read()

            result=pickle.loads(base64.b64decode(out));
            if result=="__$none__":
                return False
            elif result.has_key("list"):
                for k in result["list"]:
                    v=result["list"][k]
                    ctx=ContextObject()
                    ctx.name=k
                    if v["type"]==str(types.ModuleType):
                        ctx.type='module'
                    elif v["type"]==str(types.MethodType) or (v["type"]==str(types.BuiltinMethodType) and self.parent.type=="class"):
                        ctx.type='methode'
                    elif v["type"]==str(types.FunctionType) or v["type"]==str(types.BuiltinFunctionType):
                        ctx.type='function'
                    elif v["type"]==str(types.ClassType) or v["type"]==str(types.TypeType):
                        ctx.type='class'
                    else:
                        ctx.type='var'
                    if v.has_key("doc") and ctx.type!='var':
                        ctx.doc=v["doc"]
                    ctx.pyTypeValue=v["type"]
                    ctx.genered=True
                    self.addChild(ctx)
                if result.has_key("doc"):
                    self.doc=result["doc"]
                return True 
        else:
            print "error python not found"


    def __repr__(self):
        return self.name
    def __str__(self):
        return self.name
            
def getTypeFromString(string):
    l=dir(types)
    for t in l:
        if str(getattr(types,t))==string:
            return t
    return None
        